package cn.edu.nju.ws.sview.reasoning.schema;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;

import cn.edu.nju.ws.sview.cache.InnerSubclassCache;
import cn.edu.nju.ws.sview.cache.SubclassCache;
import cn.edu.nju.ws.sview.cache.ViewCache;
import cn.edu.nju.ws.sview.database.DBConnectionFactory;
import cn.edu.nju.ws.sview.util.MD5;

/**
 * The inner class operations
 * 
 * @author Saisai Gong
 * 
 */
public class InnerClass {
	private HashSet<Integer> members = null;
	private int id;

	/**
	 * The atomic classes of innerclass which are minimal classe
	 * 
	 * @param members
	 */
	public InnerClass(HashSet<Integer> members) {
		if (members.size() < 2) {
			throw new IllegalArgumentException(
					"The innerclass must have more than one members");
		}
		this.members = new HashSet<Integer>();
		this.members.addAll(members);
	}

	/**
	 * 获得id
	 * 
	 * @return
	 */
	public int getID() {
		if (this.id == 0) {
			this.id = getInnerClassID(this.members, false);
		}

		return this.id;
	}
	
	public void setID(int id){
		this.id = id;
	}

	/**
	 * get inner class id
	 * 
	 * @param members
	 * @createNewFlag. Whether create a new inner class when not exist
	 * @return -1 if none
	 */
	public static int getInnerClassID(HashSet<Integer> members,
			boolean createNewFlag) {
		int result = -1;

		ArrayList<Integer> list = new ArrayList<Integer>(members);
		Collections.sort(list);
		int num = members.size();
		try {
			Connection conn = DBConnectionFactory.getConnection();
			PreparedStatement stmt = conn
					.prepareStatement("select id from innerclass where member_num=? and member_hash=?");
			stmt.setInt(1, num);
			String hashStr = String.valueOf(list.hashCode());
			byte[] hash = MD5.makeMD5(hashStr);
			stmt.setBytes(2, hash);
			ResultSet rs = stmt.executeQuery();
			if (rs.next()) {
				result = rs.getInt("id");
			}
			rs.close();

			if (result < 0 && createNewFlag) {
//				PreparedStatement insertStmt = conn
//						.prepareStatement("insert into innerclass(member_num,member_hash) values(?,?)");
//				insertStmt.setInt(1, num);
//				insertStmt.setBytes(2, hash);
//				insertStmt.executeUpdate();
//
//				stmt.setInt(1, num);
//				stmt.setBytes(2, hash);
//				ResultSet resultSet = stmt.executeQuery();
//				int innerClassID = 0;
//				if (resultSet.next()) {
//					innerClassID = resultSet.getInt("id");
//				}
//				resultSet.close();
//				insertStmt.close();
//				PreparedStatement insertStmt2 = conn
//						.prepareStatement("insert into innerclass_member values(?,?)");
//				insertStmt2.setInt(1, innerClassID);
//				for (Integer classID : members) {
//					insertStmt2.setInt(2, classID);
//					insertStmt2.executeUpdate();
//				}
//				insertStmt2.close();
//
//				result = innerClassID;
				InnerClass innerClass = new InnerClass(members);
				innerClass.serialize();
				result = innerClass.getID();
			}
			stmt.close();
			conn.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}

		return result;
	}

	/**
	 * get members of innerclass
	 * 
	 * @param innerClassID
	 * @return
	 */
	public static HashSet<Integer> getMembers(int innerClassID) {
		HashSet<Integer> result = new HashSet<Integer>();

		try {
			Connection conn = DBConnectionFactory.getConnection();
			PreparedStatement stmt = conn
					.prepareStatement("select classID from innerclass_member where inner_classID=?");
			stmt.setInt(1, innerClassID);
			ResultSet rs = stmt.executeQuery();
			while (rs.next()) {
				int classID = rs.getInt("classID");
				result.add(classID);
			}
			rs.close();
			stmt.close();
			conn.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}

		return result;
	}

	/**
	 * get all the inner classes in the database
	 * 
	 * @return
	 */
	public static HashSet<Integer> getAllInnerClasses() {
		HashSet<Integer> result = new HashSet<Integer>();

		try {
			Connection conn = DBConnectionFactory.getConnection();
			Statement stmt = conn.createStatement();
			ResultSet rs = stmt.executeQuery("select id from innerclass");
			while (rs.next()) {
				int classID = rs.getInt("id");
				result.add(classID);
			}
			rs.close();
			stmt.close();
			conn.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}

		return result;
	}
	
	/**
	 * 对于一个原始类得到它的子孙内部类
	 * @param originClassID
	 * @return
	 */
	private static HashSet<MyClass> getSubInnerClassOfOrigin(int originClassID){
		HashSet<MyClass> result = new HashSet<MyClass>();

		try {
			Connection conn = DBConnectionFactory.getConnection();
			PreparedStatement stmt = conn
					.prepareStatement("select inner_classID from innerclass_member where classID=?");
			stmt.setInt(1, originClassID);
			ResultSet rs = stmt.executeQuery();
			while (rs.next()) {
				int innerClassID = rs.getInt("inner_classID");
				result.add(new MyClass(true,innerClassID));
			}
			rs.close();
			stmt.close();
			conn.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}

		return result;
	}

	public static void  computeSubClass2(int newInnerClassID){
		HashSet<Integer> innerclasses = getAllInnerClasses();
		try {
			Connection conn = DBConnectionFactory.getConnection();
			PreparedStatement stmt = conn
					.prepareStatement("insert ignore into innersubclass values(?,?)");
			for (Integer innerClassID : innerclasses) {
				if (newInnerClassID == innerClassID)
					continue;
				boolean subFlag = isSubClass(newInnerClassID, innerClassID);
				boolean superFlag = isSuperClass(newInnerClassID, innerClassID);

				if (subFlag) {
					stmt.setInt(1, newInnerClassID);
					stmt.setInt(2, innerClassID);
					stmt.executeUpdate();
					try {
						InnerSubclassCache.getInstance().updateSubClassCache(
								innerClassID, newInnerClassID);
					} catch (Throwable e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}

				if (superFlag) {
					stmt.setInt(1, innerClassID);
					stmt.setInt(2, newInnerClassID);
					stmt.executeUpdate();
					try {
						InnerSubclassCache.getInstance().updateSuperClassCache(
								innerClassID, newInnerClassID);
					} catch (Throwable e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
			stmt.close();
			conn.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * compute subclass relationship with existing innerclass in the database
	 */
	private  void computeSubClass(int newInnerClassID) {
		HashSet<Integer> innerclasses = getAllInnerClasses();
		try {
			Connection conn = DBConnectionFactory.getConnection();
			PreparedStatement stmt = conn
					.prepareStatement("insert ignore into innersubclass values(?,?)");
			for (Integer innerClassID : innerclasses) {
				if (newInnerClassID == innerClassID)
					continue;
				boolean subFlag = isSubClass(newInnerClassID, innerClassID);
				boolean superFlag = isSuperClass(newInnerClassID, innerClassID);

				if (subFlag) {
					stmt.setInt(1, newInnerClassID);
					stmt.setInt(2, innerClassID);
					stmt.executeUpdate();
					try {
						InnerSubclassCache.getInstance().updateSubClassCache(
								innerClassID, newInnerClassID);
					} catch (Throwable e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}

				if (superFlag) {
					stmt.setInt(1, innerClassID);
					stmt.setInt(2, newInnerClassID);
					stmt.executeUpdate();
					try {
						InnerSubclassCache.getInstance().updateSuperClassCache(
								innerClassID, newInnerClassID);
					} catch (Throwable e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
			stmt.close();
			conn.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	/**
	 * store the innerclass
	 * 
	 * @return false if exists
	 */
	public boolean serialize() {
		int innerClassID = InnerClass.getInnerClassID(this.members, false);
		if (innerClassID >= 0)
			return false;
		try {
			Connection conn = DBConnectionFactory.getConnection();

			int num = this.members.size();
			ArrayList<Integer> list = new ArrayList<Integer>(this.members);
			Collections.sort(list);
			String hashStr = String.valueOf(list.hashCode());
			byte[] hash = MD5.makeMD5(hashStr);

			PreparedStatement insertStmt = conn
					.prepareStatement("insert into innerclass(member_num,member_hash) values(?,?)");
			insertStmt.setInt(1, num);
			insertStmt.setBytes(2, hash);
			insertStmt.executeUpdate();
			PreparedStatement stmt = conn
					.prepareStatement("select id from innerclass where member_num=? and member_hash=?");
			stmt.setInt(1, num);
			stmt.setBytes(2, hash);
			ResultSet resultSet = stmt.executeQuery();
			if (resultSet.next()) {
				innerClassID = resultSet.getInt("id");
			}
			resultSet.close();
			insertStmt.close();
			PreparedStatement insertStmt2 = conn
					.prepareStatement("insert into innerclass_member values(?,?)");
			insertStmt2.setInt(1, innerClassID);
			for (Integer classID : members) {
				insertStmt2.setInt(2, classID);
				insertStmt2.executeUpdate();
			}
			insertStmt2.close();
			computeSubClass(innerClassID);
			stmt.close();
			conn.close();
			this.setID(innerClassID);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return true;
	}

	/**
	 * to See whether innerClassID1 is subclass of innerClassID2
	 * 
	 * @param innerClassID1
	 * @param innerClassID2
	 * @return
	 */
	public static boolean isSuperClass(int innerClassID1, int innerClassID2) {
		boolean result = true;
		HashSet<Integer> set1 = getMembers(innerClassID1);
		HashSet<Integer> set2 = getMembers(innerClassID2);
		for (Integer classID : set1) {
			try {
				HashSet<Integer> subSet = SubclassCache.getInstance()
						.getSubClass(classID);
				subSet.add(classID);
				subSet.retainAll(set2);
				if (subSet.size() == 0) {
					result = false;
					break;
				}
			} catch (Throwable e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		return result;
	}

	public static void main(String[] args) {
			System.out.println(InnerClass.isSuperClass(132, 139));
	}
	
	/**
	 * to See whether innerClassID1 is superclass of innerClassID2
	 * 
	 * @param innerClassID1
	 * @param innerClassID2
	 * @return
	 */
	public static boolean isSubClass(int innerClassID1, int innerClassID2) {
		boolean result = true;
		result = isSuperClass(innerClassID2, innerClassID1);
		return result;
	}

	/**
	 * get the top order of classes . That is the subclass is at front
	 * 
	 * @param classes
	 * @return
	 * @throws Throwable
	 */
	public static ArrayList<HashSet<MyClass>> getTopOrder(HashSet<MyClass> classes)
			throws Throwable {
		ArrayList<HashSet<MyClass>> result = new ArrayList<HashSet<MyClass>>();
		HashMap<MyClass, HashSet<MyClass>> graph = new HashMap<MyClass, HashSet<MyClass>>();
		HashSet<MyClass> superSet = new HashSet<MyClass>();
		for (MyClass classID : classes) {
			int mclassID = classID.getClassID();
			if(classID.isInner()){
				superSet.addAll(InnerClass.getAllSuperClass(mclassID)) ;
			}else{
				HashSet<Integer> msuperSet = SubclassCache.getInstance().getSuperClass(mclassID);
				for(Integer superClass:msuperSet){
					superSet.add(new MyClass(false,superClass));
				}
			}
			
			superSet.retainAll(classes);
			graph.put(classID, superSet);
		}
		HashMap<MyClass, HashSet<MyClass>> reverseGraph = new HashMap<MyClass, HashSet<MyClass>>();
		ArrayList<MyClass> finish = new ArrayList<MyClass>();
		HashSet<MyClass> visited = new HashSet<MyClass>();
		for (MyClass shit : classes) {
			if (!visited.contains(shit)) {
				firstDFS(shit, finish, graph, reverseGraph, visited);
			}
		}
		visited.clear();
		for (int i = finish.size() - 1; i >= 0; i--) {
			MyClass classID = finish.get(i);
			if (!visited.contains(classID)) {
				HashSet<MyClass> set = secondDFS(classID, reverseGraph, visited);
				result.add(set);
			}
		}
		return result;
	}
	
	/**
	 * get the origin superclasses of the innerclassID
	 * @param innerClassID
	 * @return
	 */
	public static HashSet<Integer> getOriginSuperClass(int innerClassID){
		HashSet<Integer> result = new HashSet<Integer>();
		HashSet<Integer> startSet = InnerClass.getMembers(innerClassID);
		result.addAll(startSet) ;
		
		for(Integer classID:startSet){
			HashSet<Integer> set;
			try {
				set = SubclassCache.getInstance().getSuperClass(classID);
				result.addAll(set) ;
			} catch (Throwable e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		return result;
	}
	
	/**
	 * get all superclass not contains itself
	 * @param innerClassID
	 * @return
	 */
	public static HashSet<MyClass> getAllSuperClass(int innerClassID){
		HashSet<MyClass> result = new HashSet<MyClass>();
		HashSet<Integer> originSuperClass = getOriginSuperClass(innerClassID);
		for(Integer originClassID:originSuperClass){
			result.add(new MyClass(false,originClassID)) ;
		}
		
		try {
			HashSet<Integer> innerSuperClass = InnerSubclassCache.getInstance().getSuperClass(innerClassID) ;
			for(Integer inner_classID:innerSuperClass){
				result.add(new MyClass(true,inner_classID)) ;
			}
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return result;
	}
	
	/**
	 * get superclass of origin or innerclass
	 * @param isInner.区分内部类和原始类
	 * @param classID
	 * @return
	 */
	public static HashSet<MyClass> getSuperClass(boolean isInner,int classID){
		HashSet<MyClass> result = new HashSet<MyClass>();
		
		if(!isInner){
			try {
				HashSet<Integer> superclasses = SubclassCache.getInstance().getSuperClass(classID);
			    for(Integer superClassID:superclasses){
			    	result.add(new MyClass(false,superClassID)) ;
			    }
			    result.add(new MyClass(false,classID)) ;
			} catch (Throwable e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else{
			HashSet<Integer> originSuperClass = getOriginSuperClass(classID);
			for(Integer originClassID:originSuperClass){
				result.add(new MyClass(false,originClassID)) ;
			}
			
			try {
				HashSet<Integer> innerSuperClass = InnerSubclassCache.getInstance().getSuperClass(classID) ;
				for(Integer inner_classID:innerSuperClass){
					result.add(new MyClass(true,inner_classID)) ;
				}
				result.add(new MyClass(true,classID)) ;
			} catch (Throwable e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		return result;
	}
	
	/**
	 * get subclasses of origin or innerclass
	 * @param isInner.区分内部类和原始类
	 * @param classID
	 * @return
	 */
	public static HashSet<MyClass> getSubClass(boolean isInner,int classID){
		HashSet<MyClass> result = new HashSet<MyClass>();
		
		if(!isInner){
			try {
				HashSet<Integer> subclasses = SubclassCache.getInstance().getSubClass(classID);
				subclasses.add(classID);
				for(Integer subClassID:subclasses){
			    	result.add(new MyClass(false,subClassID)) ;
                    HashSet<MyClass> set = getSubInnerClassOfOrigin(subClassID);
                    result.addAll(set);
			    }
			} catch (Throwable e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else{
			
			try {
				HashSet<Integer> innerSubClasses = InnerSubclassCache.getInstance().getSubClass(classID) ;
				for(Integer inner_classID:innerSubClasses){
					result.add(new MyClass(true,inner_classID)) ;
				}
				result.add(new MyClass(true,classID)) ;
			} catch (Throwable e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		return result;
	}	

	private static  void firstDFS(MyClass start, ArrayList<MyClass> finish,
			HashMap<MyClass, HashSet<MyClass>> graph,
			HashMap<MyClass, HashSet<MyClass>> reverseGraph,
			HashSet<MyClass> visited) {
		visited.add(start);
		Iterator<MyClass> itor = graph.get(start).iterator();
		while (itor.hasNext()) {
			MyClass neighbor = itor.next();
			addEdge(reverseGraph, neighbor, start);
			if (!visited.contains(neighbor))
				firstDFS(neighbor, finish, graph, reverseGraph, visited);
		}
		finish.add(start);
	}

	private static HashSet<MyClass> secondDFS(MyClass element,
			final HashMap<MyClass, HashSet<MyClass>> reverseGraph,
			HashSet<MyClass> visited) {
		HashSet<MyClass> result = new HashSet<MyClass>();
		visited.add(element);
		if (reverseGraph.get(element) == null) {
			result.add(element);
			return result;
		}
		Iterator<MyClass> itor = reverseGraph.get(element).iterator();
		while (itor.hasNext()) {
			MyClass neighbour = itor.next();
			if (!visited.contains(neighbour)) {
				HashSet<MyClass> set = secondDFS(neighbour, reverseGraph,
						visited);
				result.addAll(set);
			}
		}
		result.add(element);
		return result;
	}

	private static void addEdge(HashMap<MyClass, HashSet<MyClass>> reverseGraph,
			MyClass source, MyClass obj) {
		if (!reverseGraph.containsKey(source)) {
			HashSet<MyClass> list = new HashSet<MyClass>();
			list.add(obj);
			reverseGraph.put(source, list);
		} else {
			HashSet<MyClass> list = reverseGraph.get(source);
			list.add(obj);
		}
		return;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((members == null) ? 0 : members.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		InnerClass other = (InnerClass) obj;
		if (members == null) {
			if (other.members != null)
				return false;
		} else if (!members.equals(other.members))
			return false;
		return true;
	}
}
